package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
	rdb *redis.Client
	ctx = context.Background()

	// Metrics
	requestsTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "user_service_requests_total",
			Help: "Total number of requests received",
		},
		[]string{"method", "endpoint", "status"},
	)

	requestDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "user_service_request_duration_seconds",
			Help:    "Request duration in seconds",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"method", "endpoint"},
	)
)

type User struct {
	ID       string `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
	Created  int64  `json:"created"`
}

func init() {
	// Register metrics
	prometheus.MustRegister(requestsTotal)
	prometheus.MustRegister(requestDuration)

	// Setup Redis connection
	redisHost := os.Getenv("REDIS_HOST")
	if redisHost == "" {
		redisHost = "localhost"
	}
	redisPort := os.Getenv("REDIS_PORT")
	if redisPort == "" {
		redisPort = "6379"
	}
	redisPassword := os.Getenv("REDIS_PASSWORD")

	rdb = redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%s", redisHost, redisPort),
		Password: redisPassword,
		DB:       0,
	})

	// Add some sample users to Redis
	sampleUsers := []User{
		{ID: "user1", Username: "john_doe", Email: "john@example.com", Created: time.Now().Unix()},
		{ID: "user2", Username: "jane_doe", Email: "jane@example.com", Created: time.Now().Unix()},
	}

	for _, user := range sampleUsers {
		err := rdb.HMSet(ctx, "user:"+user.ID, 
			"id", user.ID,
			"username", user.Username,
			"email", user.Email,
			"created", user.Created,
		).Err()
		
		if err != nil {
			log.Printf("Failed to add sample user %s: %v", user.ID, err)
		}
	}
}

func metricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.FullPath()
		method := c.Request.Method

		// Process request
		c.Next()

		// Record metrics after request is completed
		status := fmt.Sprintf("%d", c.Writer.Status())
		duration := time.Since(start).Seconds()

		requestsTotal.WithLabelValues(method, path, status).Inc()
		requestDuration.WithLabelValues(method, path).Observe(duration)
	}
}

func traceMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Extract trace ID from header or generate a new one
		traceID := c.GetHeader("X-Trace-ID")
		if traceID == "" {
			traceID = uuid.New().String()
		}
		
		// Add the trace ID to the context
		c.Set("traceId", traceID)
		c.Header("X-Trace-ID", traceID)
		
		c.Next()
	}
}

func getUserByID(c *gin.Context) {
	userID := c.Param("id")
	traceID := c.GetString("traceId")
	
	log.Printf("[TraceID: %s] Looking up user with ID: %s", traceID, userID)
	
	// Check if user exists in Redis
	exists, err := rdb.Exists(ctx, "user:"+userID).Result()
	if err != nil {
		log.Printf("[TraceID: %s] Redis error: %v", traceID, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error", "trace_id": traceID})
		return
	}
	
	if exists == 0 {
		log.Printf("[TraceID: %s] User not found: %s", traceID, userID)
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found", "trace_id": traceID})
		return
	}
	
	// Get user data
	userData, err := rdb.HGetAll(ctx, "user:"+userID).Result()
	if err != nil {
		log.Printf("[TraceID: %s] Failed to get user data: %v", traceID, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error", "trace_id": traceID})
		return
	}
	
	// Return user data
	c.JSON(http.StatusOK, gin.H{
		"id":       userData["id"],
		"username": userData["username"],
		"email":    userData["email"],
		"created":  userData["created"],
		"trace_id": traceID,
	})
}

func healthCheck(c *gin.Context) {
	// Check Redis connection
	_, err := rdb.Ping(ctx).Result()
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"status": "unhealthy",
			"reason": "Redis connection failed",
		})
		return
	}
	
	c.JSON(http.StatusOK, gin.H{
		"status": "healthy",
		"time":   time.Now().Format(time.RFC3339),
	})
}

func main() {
	// Create a new Gin router
	r := gin.New()
	r.Use(gin.Recovery())
	r.Use(metricsMiddleware())
	r.Use(traceMiddleware())
	
	// Define API routes
	r.GET("/users/:id", getUserByID)
	r.GET("/health", healthCheck)
	
	// Create a separate router for metrics
	metrics := gin.New()
	metrics.GET("/metrics", gin.WrapH(promhttp.Handler()))
	
	// Start the servers
	go func() {
		log.Println("Starting metrics server on :9090")
		if err := metrics.Run(":9090"); err != nil {
			log.Fatalf("Failed to start metrics server: %v", err)
		}
	}()
	
	log.Println("Starting API server on :8080")
	if err := r.Run(":8080"); err != nil {
		log.Fatalf("Failed to start API server: %v", err)
	}
} 